using System;
using System.IO;
using System.Linq;

namespace FileMapper {
    public class Id3v2: Id3 {
        public Id3v2() {}

        protected override bool CheckFormat(Stream file) {
            var position = file.Position;
            try {
                file.Seek(0, SeekOrigin.Begin);
                var buffer = new byte[3];
                file.Read(buffer, 0, buffer.Length);
                if (!buffer.SequenceEqual(new byte[] { 0x49, 0x44, 0x33 })) { // ID3
                    return false;
                }
                buffer = new byte[7];
                file.Read(buffer, 0, buffer.Length);
                // supported up to version 2.4.0
                if ((0x04 < buffer[0]) || (buffer[0] == 0x04 && 0x00 < buffer[1])) {
                    return false;
                }
                var flags = (Flag)(buffer[2]);
                if (!flags.IsValid()) {
                    return false;
                }
                for (int i = 3; i < 7; ++i) {
                    if ((buffer[i] & 0x80) != 0x00) {
                        // size MSB must be cleared
                        return false;
                    }
                }
                return true;
            } finally {
                file.Position = position;
            }
        }

        protected override ExtentList GetFixedExtents(Stream file) {
            var position = file.Position;
            try {
                file.Seek(6, SeekOrigin.Begin);
                var buffer = new byte[5];
                file.Read(buffer, 0, buffer.Length);
                var flag = (Flag)(buffer[0]);
                var size = DecodeSyncSafe(buffer, 1) + (flag.IsSet(Flag.Footer) ? 20 : 10);
                return new ExtentList { new Extent { Offset = size, Size = file.Length - size } };
            } finally {
                file.Position = position;
            }
        }

        private UInt32 DecodeSyncSafe(byte[] buffer, int offset) {
            UInt32 val = 0x00000000;
            for (int i = offset; i < offset + 4; ++i) {
                val <<= 7;
                val |= buffer[i];
            }
            return val;
        }
    }

    [Flags]
    internal enum Flag: byte {
        Unsynchronized = 0x80,
        ExtendedHeader = 0x40,
        Experimental   = 0x20,
        Footer         = 0x10,
    }

    internal static class FlagExtensions {
        public static bool IsSet(this Flag flag, Flag bits) {
            return (flag & bits) != 0;
        }

        public static bool IsClear(this Flag flag, Flag bits) {
            return (flag & bits) == 0;
        }

        public static bool IsValid(this Flag flag) {
            return (flag & ~(Flag.Unsynchronized | Flag.ExtendedHeader | Flag.Experimental | Flag.Footer)) == 0;
        }
    }
}
